﻿using DotNetCommon;
using DotNetCommon.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace DBUtil.Builders;

public class SelectTreeBuilder<T, Dto> : BaseBuilder where T : class, new() where Dto : class, new()
{
    private readonly EntityInfo entityInfo;
    private readonly EntityInfo dtoEntityInfo;
    private readonly EntityPropertyInfo idEntityPropertyInfo;
    private readonly EntityPropertyInfo parentIdEntityPropertyInfo;
    private readonly EntityPropertyInfo childrenEntityPropertyInfo;

    internal SelectTreeBuilder(DBAccess db, Expression<Func<T, object>> idSelector, Expression<Func<T, object>> parentIdSelector, Expression<Func<Dto, IList<Dto>>> childrenSelector) : base(db)
    {
        AssertUtil.NotNull(idSelector);
        AssertUtil.NotNull(parentIdSelector);
        AssertUtil.NotNull(childrenSelector);
        this.entityInfo = db.GetEntityInfoInternal<T>();
        this.dtoEntityInfo = db.GetEntityInfoInternal<Dto>();

        //id
        if (idSelector.Body is UnaryExpression unary && unary.Operand is MemberExpression member)
        {
            this.idEntityPropertyInfo = entityInfo.Props.FirstOrDefault(i => i.PropNamePure == member.Member.Name);
        }
        else if (idSelector.Body is MemberExpression member2)
        {
            this.idEntityPropertyInfo = entityInfo.Props.FirstOrDefault(i => i.PropNamePure == member2.Member.Name);
        }
        AssertUtil.NotNull(this.idEntityPropertyInfo);

        //parentId
        if (parentIdSelector.Body is UnaryExpression unary2 && unary2.Operand is MemberExpression member3)
        {
            this.parentIdEntityPropertyInfo = entityInfo.Props.FirstOrDefault(i => i.PropNamePure == member3.Member.Name);
        }
        else if (parentIdSelector.Body is MemberExpression member2)
        {
            this.parentIdEntityPropertyInfo = entityInfo.Props.FirstOrDefault(i => i.PropNamePure == member2.Member.Name);
        }
        AssertUtil.NotNull(this.parentIdEntityPropertyInfo);

        //children
        if (childrenSelector.Body is UnaryExpression unary3 && unary3.Operand is MemberExpression member4)
        {
            this.childrenEntityPropertyInfo = dtoEntityInfo.Props.FirstOrDefault(i => i.PropNamePure == member4.Member.Name);
        }
        else if (childrenSelector.Body is MemberExpression member2)
        {
            this.childrenEntityPropertyInfo = dtoEntityInfo.Props.FirstOrDefault(i => i.PropNamePure == member2.Member.Name);
        }
        AssertUtil.NotNull(this.childrenEntityPropertyInfo);
    }

    internal SelectTreeBuilder(DBAccess db, string idPropName, string parentIdPropName, string childrenPropName) : base(db)
    {
        this.entityInfo = db.GetEntityInfoInternal<T>();
        this.dtoEntityInfo = db.GetEntityInfoInternal<Dto>();

        //尝试寻找 idPropName
        if (idPropName.IsNullOrWhiteSpace())
        {
            var pris = this.entityInfo.Props.Where(i => i.IsPrimaryKey).ToList();
            if (pris.Count == 1) idPropName = pris[0].PropNamePure;
        }
        if (idPropName.IsNullOrWhiteSpace())
        {
            var idProp = this.entityInfo.Props.Where(i => i.PropNamePure.Equals("Id", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (idProp != null) idPropName = idProp.PropNamePure;
        }

        //尝试寻找 parentIdPropName
        if (parentIdPropName.IsNullOrWhiteSpace())
        {
            var parent = this.entityInfo.Props.Where(i => i.IsParentId).FirstOrDefault();
            if (parent != null) parentIdPropName = parent.PropNamePure;
        }
        if (parentIdPropName.IsNullOrWhiteSpace())
        {
            var parent = this.entityInfo.Props.Where(i => i.PropNamePure.Equals("ParentId", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (parent != null) parentIdPropName = parent.PropNamePure;
        }

        //尝试寻找 childrenPropName
        if (childrenPropName.IsNullOrWhiteSpace())
        {
            var children = this.dtoEntityInfo.Props.Where(i => i.IsChildren).FirstOrDefault();
            if (children != null) childrenPropName = children.PropNamePure;
        }
        if (childrenPropName.IsNullOrWhiteSpace())
        {
            var children = this.entityInfo.Props.Where(i => i.PropNamePure.Equals("Children", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (children != null) childrenPropName = children.PropNamePure;
        }

        //id
        this.idEntityPropertyInfo = entityInfo.Props.FirstOrDefault(i => i.PropNamePure == idPropName).IfNullUse(entityInfo.Props.FirstOrDefault(i => string.Equals(i.PropNamePure, idPropName, StringComparison.OrdinalIgnoreCase)));
        AssertUtil.NotNull(this.idEntityPropertyInfo);

        //parentId
        this.parentIdEntityPropertyInfo = entityInfo.Props.FirstOrDefault(i => i.PropNamePure == parentIdPropName).IfNullUse(entityInfo.Props.FirstOrDefault(i => string.Equals(i.PropNamePure, parentIdPropName, StringComparison.OrdinalIgnoreCase)));
        AssertUtil.NotNull(this.parentIdEntityPropertyInfo);

        //children
        this.childrenEntityPropertyInfo = dtoEntityInfo.Props.FirstOrDefault(i => i.PropNamePure == childrenPropName).IfNullUse(dtoEntityInfo.Props.FirstOrDefault(i => string.Equals(i.PropNamePure, childrenPropName, StringComparison.OrdinalIgnoreCase)));
        AssertUtil.NotNull(this.childrenEntityPropertyInfo);
    }

    protected List<object> Filters { get; set; } = [];

    #region WhereSeg & Where
    public virtual SelectTreeBuilder<T, Dto> WhereSeg<TAny>(Expression<Func<TAny, bool>> filter)
    {
        AssertUtil.NotNull(filter);
        Filters.Add(filter);
        return this;
    }
    public virtual SelectTreeBuilder<T, Dto> WhereSegIf<TAny>(bool condition, Expression<Func<TAny, bool>> filter)
    {
        if (!condition) return this;
        return WhereSeg(filter);
    }
    public virtual SelectTreeBuilder<T, Dto> Where(string filter)
    {
        AssertUtil.NotNullOrWhiteSpace(filter);
        Filters.Add(filter);
        return this;
    }
    public virtual SelectTreeBuilder<T, Dto> WhereIf(bool condition, string filter)
    {
        if (!condition) return this;
        return Where(filter);
    }
    public virtual SelectTreeBuilder<T, Dto> Where(Expression<Func<T, bool>> filter)
    {
        AssertUtil.NotNull(filter);
        Filters.Add(filter);
        return this;
    }
    public virtual SelectTreeBuilder<T, Dto> WhereIf(bool condition, Expression<Func<T, bool>> filter)
    {
        if (!condition) return this;
        return Where(filter);
    }
    #endregion

    private const string MAGICSTR = "##^_^";
    private const string MAGICSTR2 = ",##^_^";
    public string ToSql()
    {
        //当没有条件时表示加载全部节点, 此时 向下/上 的扩展都没有意义
        if (Filters.Count == 0) return db.Select<T>().ToSqlList();

        var t = BuilderHelper.GetNewTableAlias("t");
        var select = db.Select<T>().Alias(t).SetFilters(Filters) as SelectBuilder<T>;
        if (parentLimit > 0 || subLimit > 0) select.AddExtraSelectSeg(MAGICSTR);
        var simpleSql = select.ToSqlList().TrimEnd(';');
        var t2 = BuilderHelper.GetNewTableAlias("t2");
        var simpleSqlClause = db.Select<T>().Alias(t2).ToSqlSelectClause();
        var cols = entityInfo.Props.Where(i => !i.IsIgnoreSelect).Select(i => i.PropNameQuoted).ToStringSeparated(",");
        var sb = new StringBuilder().Append("with recursive ");

        //parentCte
        var parentSimpleSql = simpleSql;
        if (parentLimit <= 0 && subLimit > 0)
        {
            parentSimpleSql = parentSimpleSql.Replace(MAGICSTR2, "");
        }
        else if (parentLimit > 0)
        {
            parentSimpleSql = parentSimpleSql.Replace(MAGICSTR, "0");
        }
        var parentCte = $"""
            cteParent ({cols}{(parentLimit > 0 ? ",`_parentLimit`" : "")}) AS
            (
              {parentSimpleSql}
              union
              select {simpleSqlClause}{(parentLimit > 0 ? ",`_parentLimit`+1" : "")} from {entityInfo.TableName.FullNameQuoted} {t2},cteParent where cteParent.{parentIdEntityPropertyInfo.PropNameQuoted}={t2}.{idEntityPropertyInfo.ColumnNameQuoted}{(parentLimit > 0 ? $" and `_parentLimit`<{parentLimit}" : "")}
            )
            """;

        //subCte
        var subSimpleSql = simpleSql;
        if (subLimit <= 0 && parentLimit > 0)
        {
            subSimpleSql = subSimpleSql.Replace(MAGICSTR2, "");
        }
        else if (subLimit > 0)
        {
            subSimpleSql = subSimpleSql.Replace(MAGICSTR, "0");
        }
        var childrenCte = $"""
            cteChildren ({cols}{(subLimit > 0 ? ",`_subLimit`" : "")}) AS
            (
              {subSimpleSql}
              union
              select {simpleSqlClause}{(subLimit > 0 ? ",`_subLimit`+1" : "")} from {entityInfo.TableName.FullNameQuoted} {t2},cteChildren where cteChildren.{idEntityPropertyInfo.PropNameQuoted}={t2}.{parentIdEntityPropertyInfo.ColumnNameQuoted}{(subLimit > 0 ? $" and `_subLimit`<{subLimit}" : "")}
            )
            """;

        if (spreedMode == EnumTreeSpreedMode.Both)
        {
            sb.Append(parentCte).AppendLine(",").Append(childrenCte).AppendLine()
                .AppendLine("select * from cteParent").AppendLine("union").Append("select * from cteChildren;");
        }
        else if (spreedMode == EnumTreeSpreedMode.Parent)
        {
            sb.Append(parentCte).AppendLine().Append("select * from cteParent;");
        }
        else
        {
            sb.Append(childrenCte).AppendLine().Append("select * from cteChildren;");
        }
        var sql = sb.ToString();
        sb.Clear();
        return sql;
    }

    public List<Dto> ToList()
    {
        var sql = ToSql();
        var list = db.SelectModelList<Dto>(sql);
        var compare = parentIdEntityPropertyInfo.Type.GetDefaultEqualityComparer();
        var tree = list.FetchToTree(idEntityPropertyInfo.PropNamePure, parentIdEntityPropertyInfo.PropNamePure, childrenEntityPropertyInfo.PropNamePure, compare);
        if (visitAction != null) tree.VisitTree(childrenEntityPropertyInfo.PropNamePure, visitAction);
        return tree;
    }

    public async Task<List<Dto>> ToListAsync()
    {
        var sql = ToSql();
        var list = await db.SelectModelListAsync<Dto>(sql);
        var compare = parentIdEntityPropertyInfo.Type.GetDefaultEqualityComparer();
        var tree = list.FetchToTree(idEntityPropertyInfo.PropNamePure, parentIdEntityPropertyInfo.PropNamePure, childrenEntityPropertyInfo.PropNamePure, compare);
        if (visitAction != null) tree.VisitTree(childrenEntityPropertyInfo.PropNamePure, visitAction);
        return tree;
    }

    private EnumTreeSpreedMode spreedMode = EnumTreeSpreedMode.Both;
    public SelectTreeBuilder<T, Dto> SetSpreedMode(EnumTreeSpreedMode mode)
    {
        this.spreedMode = mode;
        return this;
    }

    private int subLimit;
    private int parentLimit;
    public SelectTreeBuilder<T, Dto> SetSpreedLimit(int limit, EnumTreeSpreedMode mode)
    {
        if (mode == EnumTreeSpreedMode.Sub)
        {
            this.subLimit = limit;
        }
        else if (mode == EnumTreeSpreedMode.Parent)
        {
            this.parentLimit = limit;
        }
        else
        {
            this.subLimit = this.parentLimit = limit;
        }
        return this;
    }

    private Action<VisitTreeContext<Dto>> visitAction = null;
    public SelectTreeBuilder<T, Dto> VisitTreeInMemory(Action<VisitTreeContext<Dto>> action)
    {
        this.visitAction = action;
        return this;
    }
}

public class SelectTreeBuilder<T> : SelectTreeBuilder<T, T> where T : class, new()
{
    internal SelectTreeBuilder(DBAccess db, Expression<Func<T, object>> idSelector, Expression<Func<T, object>> parentIdSelector, Expression<Func<T, IList<T>>> childrenSelector) : base(db, idSelector, parentIdSelector, childrenSelector)
    {
    }
    internal SelectTreeBuilder(DBAccess db, string idPropName, string parentIdPropName, string childrenPropName) : base(db, idPropName, parentIdPropName, childrenPropName)
    {
    }
}
